/************************************************
 * MirrorOS Kernel Drivers
 * Fat FS Driver
 * Copyright 2008 Matthew Iselin
 ***********************************************/

// Includes
#include "fat.h"

// Functions

// appends a specific cluster to the given cluster, assumes that endclus is the last one
void append_cluster( struct fat_fs fs, unsigned int clus, unsigned int endclus )
{
	// set them up properly
	if( clus != endclus )
		set_clus_entry( fs, clus, endclus );
	set_clus_entry( fs, endclus, 0x0FFFFFF8 );
}

// finds the cluster and cluster position for a specific offset in a file
void get_cluster_from_file_offset( struct fat_fs fs, unsigned int offset, unsigned int first_clus, unsigned int* clus, unsigned int* clus_pos )
{
	// get the size in bytes of a cluster
	unsigned int clus_size = fs.bpb.BPB_SecPerClus * fs.bpb.BPB_BytsPerSec;
	
	// try to find the position
	int j = 0;
	first_clus = get_clus_entry( fs, first_clus );
	while( ! ( first_clus >= 0x0FFFFFF8 ) )
	{
		if( j * clus_size <= offset && ((j+1) * clus_size) > offset )
			break;
		j++;
		first_clus = get_clus_entry( fs, first_clus );
	}
	
	// set the passed pointers
	*clus = first_clus;
	*clus_pos = j * clus_size;
}

// we need this in this file because it's also so big
int fat_internal_sys_write_file( struct fat_fs fs, struct intdesc desc, char* buff, int count )
{
#if DEBUG
	dprintf( "in write file\n" );
#endif
	
	// to make life easy, we set some stuff up here first
	unsigned int offset = desc.desc_offset;
	unsigned int file_first_clus = desc.dir_entry.DIR_FstClusLO | (desc.dir_entry.DIR_FstClusHI << 16);
	
	// we need to firsly check for a sector boundary break
	
	// the final offset into the file
	unsigned int totbytes = offset + count;
	
	// the sector of the offset's sector
	unsigned int offsect = offset / fs.bpb.BPB_BytsPerSec;
	
	// sector of the final offset
	unsigned int allsect = totbytes / fs.bpb.BPB_BytsPerSec;
	
	// number of bytes sys_read
	unsigned int nb = 0;

	// find out which sector (non-relative)
	unsigned int realsect = first_sector_of_cluster( fs, file_first_clus ) + offsect;
	
	// have we enough clusters?
	int i = fs.bpb.BPB_SecPerClus * fs.bpb.BPB_BytsPerSec;
	int j = desc.dir_entry.DIR_FileSize / i;
	if( desc.dir_entry.DIR_FileSize % i )
		j++;
	if( j == 0 )
		j = 1; // we always have one cluster
	
	// get endpoints
	int endPosCluster = j * i; // the end cluster offset
	int endPosFile = offset + count; // the end of our sys_write area
	int numExtraBytes = 0;
	
	// open the device
#if DEBUG
	dprintf( "opening %s\n", fs.mount );
#endif
	desc.desc = fs.desc = sys_open( fs.mount, 0 );
#if DEBUG
	dprintf( "got %d\n", fs.desc );
	dprintf( "%d, %d, %d\n", endPosFile, endPosCluster, file_first_clus );
#endif
	
	// do we need more clusters?
	if( endPosFile > endPosCluster || file_first_clus == 0 )
	{
		// how many extra bytes do we need?
		numExtraBytes = endPosFile - endPosCluster;
		
#if DEBUG
		dprintf( "numExtraBytes = %d\n", numExtraBytes );
#endif
			
		// convert to clusters
		j = numExtraBytes / i;
		if( numExtraBytes % i )
			j++;
		
		// get the last cluster
		int lastCluster = 0;
		if( file_first_clus != 0 )
		{
			unsigned int clus = get_clus_entry( fs, file_first_clus );
#if DEBUG
			dprintf( "looping through clus chain\n" );
#endif
			while( ! ( clus >= 0x0FFFFFF8 ) )
			{
				// increment our count
				lastCluster = clus;
				
				// get the next entry
				clus = get_clus_entry( fs, clus );
			}
#if DEBUG
			dprintf( "done\n" );
#endif
		}
		else
			lastCluster = 0;
		
		// now set the new last cluster
		unsigned int prev;
#if DEBUG
		dprintf( "i = %d, j = %d\n", i, j );
#endif
		for( i = 0; i < j; i++ )
		{
			// find a free cluster, append it to the chain
			prev = lastCluster;
#if DEBUG
			dprintf( "searching for free cluster\n" );
#endif
			lastCluster = find_free_clus( fs );
#if DEBUG
			dprintf( "found one\n" );
#endif
			
			// append it
			if( file_first_clus == 0 && i == 0 )
			{
				file_first_clus = lastCluster;
#if DEBUG
				dprintf( "setting...\n" );
#endif
				set_clus_entry( fs, lastCluster, 0x0FFFFFF8 );
			}
			else
				set_clus_entry( fs, prev, lastCluster );
		}
		
		// extremely important to ensure that the last cluster points to EOC
#if DEBUG
		dprintf( "setting last cluster to eoc\n" );
#endif
		set_clus_entry( fs, lastCluster, 0x0FFFFFF8 );
	}
	
#if DEBUG
	dprintf( "done cluster maintenance\n" );
#endif
	
	// cluster position and number
	unsigned int clusterPos,fileCluster;
	get_cluster_from_file_offset( fs, offset, file_first_clus, &clusterPos, &fileCluster );
	
	// set the skips
	unsigned int sectorSkip = ( offsect % fs.bpb.BPB_SecPerClus );
	unsigned int clusterSkip = offsect / fs.bpb.BPB_SecPerClus;
	unsigned int dataSkip = offset % fs.bpb.BPB_BytsPerSec;
	
	// the current position in the buffer
	unsigned int bufferPos = 0;
	
	// get the cluster chain
	unsigned int* cluschain = (unsigned int*) malloc( sizeof( unsigned int* ) * 4096 ); // maximum of 4096 links
	int cluscnt = 0; // there will always be 1 - EOF
	cluschain[cluscnt++] = file_first_clus;
	unsigned int myclus = get_clus_entry( fs, file_first_clus );
	while( ! ( myclus >= 0x0FFFFFF8 ) )
	{
		cluschain[cluscnt++] = myclus;
		myclus = get_clus_entry( fs, myclus );
	}
	cluschain[cluscnt++] = myclus;
	
	// sector buffer
	char* sec = (char*) malloc( fs.bpb.BPB_BytsPerSec );
	unsigned int bufSize = fs.bpb.BPB_BytsPerSec;
	
	// loop through each cluster
	for( i = clusterSkip; i < cluscnt && count > 0; i++ )
	{
		// the start of this cluster
		unsigned int startSector = first_sector_of_cluster( fs, cluschain[i] );
		
		// process every sector
		for( j = 0 + sectorSkip; j < fs.bpb.BPB_SecPerClus && count > 0; j++ )
		{			
			// sys_read in the sector
			sys_seek( desc.desc, startSector + j, SEEK_SET );
			sys_read( desc.desc, sec, 1 );
			
			// check that we have room
			if( count < bufSize )
				bufSize = count + dataSkip;
			if( bufSize > 512 )
				bufSize = 512;
			
			// copy the data
			char* ptr = (char*) buff;
			memcpy( (char*) (sec + dataSkip), (char*) (ptr+bufferPos), bufSize - dataSkip );
			
			// sys_write the data
			sys_seek( desc.desc, startSector + j, SEEK_SET );
			sys_write( desc.desc, sec, 1 );
			
			// decrement the size
			count -= (bufSize - dataSkip);
			bufferPos += (bufSize - dataSkip);
			dataSkip = 0;
			bufSize = 512;
		}
		sectorSkip = 0;
	}
	
	// update the directory entry, if needed
	if( bufferPos )
	{
		// difference between the old size and the new size
		numExtraBytes = totbytes - desc.dir_entry.DIR_FileSize;
		if( numExtraBytes < 0 )
			numExtraBytes = 0;
		
		// check if it's more than 0
		if( numExtraBytes > 0 )
		{			
			// sys_read in the directory sector
			sys_seek( desc.desc, desc.dir_sector, SEEK_SET );
			sys_read( desc.desc, sec, 1 );
	
			// set the new entry
			struct FAT_DirEnt* myEnt = (struct FAT_DirEnt*) &sec[desc.dir_offset];
			desc.dir_entry.DIR_FileSize += numExtraBytes;
			desc.dir_entry.DIR_FstClusLO = (file_first_clus & 0xFFFF);
			desc.dir_entry.DIR_FstClusHI = (file_first_clus >> 16) & 0xFFFF;
			*myEnt = desc.dir_entry;
			
			// sys_write the data back
			sys_seek( desc.desc, desc.dir_sector, SEEK_SET );
			int num = sys_write( desc.desc, sec, 1 );
		}
	}
	
	// free the cluster chain and the sector buffer
	free( sec );
	free( cluschain );
	
	// close the device
	sys_close( desc.desc );
	fs.desc = -1;

	// return the new buffer position
	return bufferPos;
}
